#include "SEL.h"
#include "SEL_painter.h"
#include "SEL_resources.h"

/*
 * TODO - complete test !
 */

using namespace SEL;

#define MAP_RGBA8888(r, g, b, a) ((r<<24)|(g<<16)|(b<<8)|(a))

class QuitEventHandler : public EventHandler
{
	virtual bool 
	handleQuitEvent ()
	{
		exit (0);
		return true;
	}
};

class KeyEventHandler : public EventHandler
{
public:
	bool key_pressed;
	bool quit;
	
	KeyEventHandler () : key_pressed (false), quit (false) { }

	virtual bool 
	handleKeyPressEvent (SDL::keysym &keysym)
	{
		key_pressed = true;
		return true;
	}

	virtual bool 
	handleQuitEvent ()
	{
		quit = true;
		return true;
	}
};

class App
{
public:
	Display *display;
	int bpp;
	int width;
	int height;
	int flags;

	Font *font_white;
	Font *font_big;

	App ();
	~App ();    

	// this function returns false if quit event has occured,
	// otherwise returns true
	bool waitKey ();

	void parseCommandLine (int argc, char **argv);
	
	int rnd () { return rand (); }
	
	int main (int argc, char **argv);

	void drawGrid ();
	bool doIntroScreen ();
	bool doPutPixelTest ();
	bool doLinesAndRectanglesTest ();
};

App::App ()
	: display (NULL), bpp (16), width (640), height (480), 
	  flags (SDL::SWSURFACE)
{
}

App::~App ()
{
}

bool
App::waitKey ()
{
	KeyEventHandler key_handler;
	
	while (!key_handler.key_pressed && !key_handler.quit)
	{
		Event::handleEvents (key_handler);
	}

	return !key_handler.quit;
}

void 
App::parseCommandLine (int argc, char **argv)
{
	for (int i = 1; i < argc; ++i)
	{
		if (strcmp (argv [i], "-bpp") == 0 && i < argc - 1)
		{
			++i;
			bpp = atoi (argv [i]);
			if (bpp != 16 && bpp != 24 && bpp != 32)
			{
				std::cerr << "Invalid bpp specified!" << std::endl;
				exit (1);
			}
		}
		else if (strcmp (argv [i], "-fullscreen") == 0)
		{
			flags |= SDL::FULLSCREEN;
		}
		else if (strcmp (argv [i], "-hwsurface") == 0)
		{
			flags |= SDL::HWSURFACE;
		}
		else if (strcmp (argv [i], "-res") == 0 &&
			 i < argc - 2)
		{
			++i;
			width = atoi (argv [i]);
			++i;
			height = atoi (argv [i]);
		}
		else if (strcmp (argv [i], "-h") == 0 ||
			 strcmp (argv [i], "--help") == 0)
		{
			std::cout << "USAGE:" << std::endl;
			std::cout << argv [0] << " [-bpp (depth)] [-fullscreen] [-hwsurface] [-res (width) (height)]" << std::endl;
			exit (0);
		}
	}
}

int 
App::main (int argc, char **argv)
{
    try 
    {

	if (argc > 1)
		parseCommandLine (argc, argv);

	init (INIT_VIDEO);

	resourcesInit ();
	
	atexit (quit);

	display = new Display (width, height, bpp, flags);

	QuitEventHandler quit_handler;

	ResourceManager manager ("resources.rs");
	
	// load all resources
	manager.loadAll ();
	
	// The fonts are owned by the resource manager.
	font_white = getFont (manager.get ("fonts/font_white"));
	font_big   = getFont (manager.get ("fonts/font_big"));

	font_white->setTargetSurface (display);
	font_big->setTargetSurface (display);

	if (!doIntroScreen ())
		goto quit;
	if (!doPutPixelTest ())
		goto quit;

	if (!doLinesAndRectanglesTest ())
		goto quit;		
		
quit:		
	;
    } 
    catch (Error & error) 
    {
	std::cout << "Exception caught: " << error.str () << std::endl;
    }
	
	return 0;	
};

void
App::drawGrid ()
{
	Painter painter (display);
	painter.setAutoLock (false);

	for (Uint16 i = 0; i < display->w (); i += 16)
	{
		painter.drawVLine 
			 (i, 0, display->h (), display->mapRGB (0, 0, 192));
	}

	for (Uint16 i = 0; i < display->h (); i += 16)
	{
		painter.drawHLine 
			 (0, i, display->w (),
			 display->mapRGB (128 - (i * 128 / display->h ()),
			 128 - (i * 128 / display->h ()), 
			 (i * 128 / display->h ()) + 64));
	}
}

bool
App::doIntroScreen ()
{
	Painter p_bar (display);
	p_bar.setAutoLock (false);
	p_bar.setMode (Painter::ModeBlended);

	const int delta = 1;

	Uint16 bar_x (10), bar_y (10);
	const Uint16 bar_w = 200;
	const Uint16 bar_h = 200;
	Sint16 bar_dx (delta), bar_dy (delta);

	Uint32 bar_color;
	Uint8 alpha = 0;
	Sint8 alpha_d = 1;
	
	char *text = "Press any key to start the test!";
	
	const Uint16 text_w = font_big->computeWidth (text);
	const Uint16 text_h = font_big->computeHeight (text);
	Uint16 text_x = display->w () - text_w - 10;
	Uint16 text_y = display->h () - text_h - 10;
	Sint16 text_dx (-delta), text_dy (-delta);
	
	display->fill (0, 0, 0);
	
	bool first_time = true;

	KeyEventHandler key_handler;
	
	while (!key_handler.key_pressed && !key_handler.quit)
	{
		if (!first_time)
		{
			// clean old bar
			display->fillRect (Rect (bar_x, bar_y, bar_w, bar_h),
					   0, 0, 0);
			// clean old text
			display->fillRect (Rect (text_x, text_y, text_w, text_h),
					   0, 0, 0);
					   
			// move bar
			if ((bar_dx > 0) && 
			    (bar_x + bar_w + bar_dx > display->w ()))
				bar_dx = - bar_dx;
			else if ((bar_dx < 0) &&
				 (bar_x + bar_dx < 0))
				bar_dx = -bar_dx;
				
			if ((bar_dy > 0) && 
			    (bar_y + bar_h + bar_dy > display->h ()))
				bar_dy = - bar_dy;
			else if ((bar_dy < 0) &&
				 (bar_y + bar_dy < 0))
				bar_dy = -bar_dy;
				
			bar_x += bar_dx;
			bar_y += bar_dy;

			// move text
			if ((text_dx > 0) && 
			    (text_x + text_w + text_dx > display->w ()))
				text_dx = - text_dx;
			else if ((text_dx < 0) &&
				 (text_x + text_dx < 0))
				text_dx = -text_dx;
				
			if ((text_dy > 0) && 
			    (text_y + text_h + text_dy > display->h ()))
				text_dy = - text_dy;
			else if ((text_dy < 0) &&
				 (text_y + text_dy < 0))
				text_dy = -text_dy;
				
			text_x += text_dx;
			text_y += text_dy;			

			display->lock ();
			drawGrid ();
			display->unlock ();
		}
		else
		{
			display->lock ();
			drawGrid ();
			display->unlock ();
			display->updateRect (0, 0, 0, 0);

			first_time = false;
		}


		alpha += alpha_d;
		bar_color = MAP_RGBA8888 (255, 255, 255, alpha);
		if (alpha == 128 || alpha == 0)
			alpha_d = -alpha_d;			

		display->lock ();
		p_bar.fillRect (bar_x, bar_y, bar_w, bar_h, bar_color);		
		display->unlock ();

		font_big->print (text_x, text_y, text);

		Rect r1 = Rect (bar_x - delta, bar_y - delta, 
				bar_w + 2 * delta, 
				bar_h + 2 * delta);
				
		r1.intersect (Rect (0, 0, display->w (), display->h ()));

		display->updateRect (r1.x, r1.y, r1.w, r1.h);
		
		r1 = Rect (text_x - delta, text_y - delta, 
			   text_w + 2 * delta, text_h + 2 * delta);
			   
		r1.intersect (Rect (0, 0, display->w (), display->h ()));

		display->updateRect (r1.x, r1.y, r1.w, r1.h);
			
		Event::handleEvents (key_handler);
	}
	
	return !key_handler.quit;
}

bool
App::doPutPixelTest ()
{
	display->fill (0, 0, 0);
	drawGrid ();
	font_white->print (10, 3, "Put pixel test:");

	Painter p_solid (display);
	p_solid.setAutoLock (false);
	
	Painter p_blended (display);
	p_blended.setAutoLock (false);
	p_blended.setMode (Painter::ModeBlended);
	
	display->lock ();
	
	for (int i = 300; i < 555; i+=32)
		for (int j = 0; j < 16; ++j)
		{
			p_solid.drawVLine (i + j, 20, 265,
					   display->mapRGB (32, 32, 96));
		}
	
	for (int i = 0; i < 256; ++i)
		for (int j = 0; j < 255; ++j)
		{
			p_solid.putPixel (10 + i, 25 + j,
					  display->mapRGB (i, j, 
					  255 - (j + i) / 2));
			p_blended.putPixel (300 + i, 25 + j,
					    MAP_RGBA8888 (i, j, 
					    255 - (j + i) / 2,
					     (j + i) / 2));
		}
	
	display->unlock ();

	display->flip ();

	if (!waitKey ())
		return false;

	display->fill (0, 0, 0);
	drawGrid ();
	font_white->print (10, 3, "Put pixel test (clipped):");
	
	display->lock ();

	p_solid.drawRect (99, 49, display->w () - 198, 152,
			  display->mapRGB (255, 255, 255));
	p_solid.setClipRect (Rect (100, 50, display->w () - 200, 150));
	p_blended.setClipRect (Rect (100, 50, display->w () - 200, 150));	
	
	for (int i = 300; i < 555; i+=32)
		for (int j = 0; j < 16; ++j)
		{
			p_solid.drawVLine (i + j, 20, 265,
					   display->mapRGB (32, 32, 96));
		}
	
	for (int i = 0; i < 256; ++i)
		for (int j = 0; j < 255; ++j)
		{
			p_solid.putPixel (10 + i, 25 + j,
					  display->mapRGB (i, j, 
					  255 - (j + i) / 2));
			p_blended.putPixel (300 + i, 25 + j,
					    MAP_RGBA8888 (i, j, 
					    255 - (j + i) / 2,
					     (j + i) / 2));
		}
	
	display->unlock ();

	display->flip ();
	
	if (!waitKey ())
		return false;	

	return true;
}

bool
App::doLinesAndRectanglesTest ()
{
	display->fill (0, 0, 0);
	drawGrid ();
	font_white->print (10, 3, "Lines and rectangles test:");

	Painter p_solid (display);
	p_solid.setAutoLock (false);
	
	Painter p_blended (display);
	p_blended.setAutoLock (false);
	p_blended.setMode (Painter::ModeBlended);
	
	display->lock ();

	p_solid.drawRect (9, 24, 258, 258,
			  display->mapRGB (255, 255, 255));
			  
	p_solid.fillRect (10, 25, 256, 256,
			  display->mapRGB (32, 32, 96));

	p_blended.drawRect (299, 24, 258, 258,
			    MAP_RGBA8888 (255, 255, 255, 128));
			  
	p_blended.fillRect (300, 25, 256, 256,
			    MAP_RGBA8888 (32, 32, 96, 192));

	for (int i=0; i < 256; i+=8)
	{
		p_solid.drawLine (10, 25, 10 + i, 280,
				  display->mapRGB (i, 255 - i,i));
		p_solid.drawLine (10, 25, 265, 25 + i,
				  display->mapRGB (i, 255 - i,i));
				  
		p_blended.drawLine (300, 25, 300 + i, 280,
				    MAP_RGBA8888 (i, 255 - i, i, 96 + (i / 2)));
		p_blended.drawLine (300, 25, 555, 25 + i,
				    MAP_RGBA8888 (i, 255 - i, i, 96 + (i / 2)));
	}
	
	p_solid.drawLine (10, 25, 265, 280,
			  display->mapRGB (255, 0, 255));

	p_blended.drawLine (300, 25, 555, 280,
			    MAP_RGBA8888 (255, 0, 255, 192));

	display->unlock ();
	
	display->flip ();	

	if (!waitKey ())
		return false;

	display->fill (0, 0, 0);
	drawGrid ();
	font_white->print (10, 3, "Lines and rectangles test (clipped):");

	display->lock ();

	p_solid.drawRect (99, 49, display->w () - 198, 152,
			  display->mapRGB (255, 255, 255));
	p_solid.setClipRect (Rect (100, 50, display->w () - 200, 150));
	p_blended.setClipRect (Rect (100, 50, display->w () - 200, 150));	

	p_solid.drawRect (9, 24, 258, 258,
			  display->mapRGB (255, 255, 255));
			  
	p_solid.fillRect (10, 25, 256, 256,
			  display->mapRGB (32, 32, 96));

	p_blended.drawRect (299, 24, 258, 258,
			    MAP_RGBA8888 (255, 255, 255, 128));
			  
	p_blended.fillRect (300, 25, 256, 256,
			    MAP_RGBA8888 (32, 32, 96, 192));

	for (int i=0; i < 256; i+=8)
	{
		p_solid.drawLine (10, 25, 10 + i, 280,
				  display->mapRGB (i, 255 - i,i));
		p_solid.drawLine (10, 25, 265, 25 + i,
				  display->mapRGB (i, 255 - i,i));
				  
		p_blended.drawLine (300, 25, 300 + i, 280,
				    MAP_RGBA8888 (i, 255 - i, i, 96 + (i / 2)));
		p_blended.drawLine (300, 25, 555, 25 + i,
				    MAP_RGBA8888 (i, 255 - i, i, 96 + (i / 2)));
	}
	
	p_solid.drawLine (10, 25, 265, 280,
			  display->mapRGB (255, 0, 255));

	p_blended.drawLine (300, 25, 555, 280,
			    MAP_RGBA8888 (255, 0, 255, 192));

	display->unlock ();
	
	display->flip ();	

	if (!waitKey ())
		return false;
	
	return true;
};

int 
main (int argc, char **argv)
{
	App app;
	return app.main (argc, argv);
}
